library(tidyverse) # Data manipulation
library(lubridate) # Date handlingClinical Data Journey(Phase 1): Raw Demographics to SDTM DM using R
Objective
In this article, we’ll convert a raw demographics dataset into an SDTM DM domain using R, following CDISC SDTM standards.
Raw Data
The raw dataset contains study-specific variables collected from the EDC(Electronic Data Capture) system. Before submission, these variables must be standardized into SDTM.
Load Required Packages
Import the data
raw_dm <- read_csv(
"RAW_Demo.csv",
show_col_types = FALSE
)Review the Raw Dataset
head(raw_dm)# A tibble: 6 × 15
STUDYID SITEID Patient_ID Age Gender RACE ETHNICITY HEIGHT_CM WEIGHT_KG
<chr> <chr> <chr> <dbl> <chr> <chr> <chr> <dbl> <dbl>
1 ONCO-2026 SITE004 SUBJ0001 62 Male WHITE NOT HISP… 167. 78.2
2 ONCO-2026 SITE014 SUBJ0002 84 Female BLACK… NOT HISP… 164. 100.
3 ONCO-2026 SITE010 SUBJ0003 71 Female WHITE NOT HISP… 168 79.6
4 ONCO-2026 SITE007 SUBJ0004 49 Female ASIAN NOT HISP… 174 79.8
5 ONCO-2026 SITE002 SUBJ0005 45 Female ASIAN NOT HISP… 154. 73.2
6 ONCO-2026 SITE002 SUBJ0006 64 Female BLACK… NOT HISP… 145 92.4
# ℹ 6 more variables: BMI <dbl>, BIRTH_DATE <chr>, COUNTRY <chr>,
# RAND_DATE <chr>, ARM <chr>, TREATMENT_START_DATE <chr>
Initial Data Assessment
Variable names
names(raw_dm) [1] "STUDYID" "SITEID" "Patient_ID"
[4] "Age" "Gender" "RACE"
[7] "ETHNICITY" "HEIGHT_CM" "WEIGHT_KG"
[10] "BMI" "BIRTH_DATE" "COUNTRY"
[13] "RAND_DATE" "ARM" "TREATMENT_START_DATE"
Number of subjects
n_distinct(raw_dm$Patient_ID)[1] 500
Check duplicate subjects
raw_dm %>%
count(Patient_ID) %>%
filter(n > 1)# A tibble: 0 × 2
# ℹ 2 variables: Patient_ID <chr>, n <int>
Missing values
colSums(is.na(raw_dm)) STUDYID SITEID Patient_ID
0 0 0
Age Gender RACE
0 0 0
ETHNICITY HEIGHT_CM WEIGHT_KG
0 7 11
BMI BIRTH_DATE COUNTRY
18 0 0
RAND_DATE ARM TREATMENT_START_DATE
0 0 0
Missing values were observed in HEIGHT_CM, WEIGHT_KG, and BMI. These variables are not used in the SDTM DM domain, so they do not affect the DM transformation.
Raw-to-SDTM Variable Mapping
| Raw Variable | SDTM DM Variable | Transformation |
|---|---|---|
| STUDYID | STUDYID | Direct mapping |
| Patient_ID | SUBJID | Rename |
| STUDYID + SITEID + Patient_ID | USUBJID | Derive unique subject identifier |
| Gender | SEX | Convert Male/Female → M/F |
| ETHNICITY | ETHNIC | Rename |
| BIRTH_DATE | BRTHDTC | Convert to ISO 8601 |
| TREATMENT_START_DATE | RFXSTDTC | Convert to ISO 8601 |
| ARM | ARM | Direct mapping |
| ARM | ARMCD | Create coded value |
| SITEID | SITEID | Direct mapping |
| COUNTRY | COUNTRY | Direct mapping |
Note:
CDISC SDTM requires controlled terminology.Raw values such as “Male” and “Female” are standardized to “M” and “F”.
Dates were converted to the ISO 8601 format (YYYY-MM-DD), which is required by CDISC SDTM.
SDTM Transformation Approach:
The raw demographics data is transformed into the SDTM DM structure by deriving standard variables, applying CDISC controlled terminology, converting dates into ISO 8601 format, and creating unique subject identifiers required for regulatory submission.
Transform Raw Data into SDTM DM
DM <- raw_dm %>%
mutate(
DOMAIN = "DM",
USUBJID = paste(
STUDYID,
SITEID,
Patient_ID,
sep = "-"
),
SUBJID = Patient_ID,
SEX = case_when(
Gender == "Male" ~ "M",
Gender == "Female" ~ "F",
TRUE ~ NA_character_
),
AGE = Age,
ETHNIC = ETHNICITY,
ARMCD = case_when(
ARM == "ARM A" ~ "A",
ARM == "ARM B" ~ "B",
TRUE ~ NA_character_
),
BRTHDTC =
format(
dmy(BIRTH_DATE),
"%Y-%m-%d"
),
RFXSTDTC =
format(
dmy(TREATMENT_START_DATE),
"%Y-%m-%d"
)
)Select SDTM Standard Variables
DM <- DM %>%
select(
STUDYID,
DOMAIN,
USUBJID,
SUBJID,
SITEID,
AGE,
SEX,
RACE,
ETHNIC,
BRTHDTC,
COUNTRY,
ARMCD,
ARM,
RFXSTDTC
)Review SDTM DM
glimpse(DM)Rows: 500
Columns: 14
$ STUDYID <chr> "ONCO-2026", "ONCO-2026", "ONCO-2026", "ONCO-2026", "ONCO-202…
$ DOMAIN <chr> "DM", "DM", "DM", "DM", "DM", "DM", "DM", "DM", "DM", "DM", "…
$ USUBJID <chr> "ONCO-2026-SITE004-SUBJ0001", "ONCO-2026-SITE014-SUBJ0002", "…
$ SUBJID <chr> "SUBJ0001", "SUBJ0002", "SUBJ0003", "SUBJ0004", "SUBJ0005", "…
$ SITEID <chr> "SITE004", "SITE014", "SITE010", "SITE007", "SITE002", "SITE0…
$ AGE <dbl> 62, 84, 71, 49, 45, 64, 39, 83, 74, 51, 34, 76, 56, 75, 35, 4…
$ SEX <chr> "M", "F", "F", "F", "F", "F", "F", "F", "F", "F", "F", "F", "…
$ RACE <chr> "WHITE", "BLACK OR AFRICAN AMERICAN", "WHITE", "ASIAN", "ASIA…
$ ETHNIC <chr> "NOT HISPANIC OR LATINO", "NOT HISPANIC OR LATINO", "NOT HISP…
$ BRTHDTC <chr> "1961-02-22", "1939-11-19", "1953-01-03", "1974-08-28", "1978…
$ COUNTRY <chr> "GBR", "BEL", "AUS", "JPN", "USA", "USA", "USA", "ESP", "JPN"…
$ ARMCD <chr> "B", "A", "B", "B", "A", "B", "A", "A", "B", "A", "A", "B", "…
$ ARM <chr> "ARM B", "ARM A", "ARM B", "ARM B", "ARM A", "ARM B", "ARM A"…
$ RFXSTDTC <chr> "2024-04-22", "2024-11-08", "2024-05-31", "2024-02-10", "2024…
Validate the SDTM DM Dataset
Check 1: Duplicate Subjects
anyDuplicated(DM$USUBJID)[1] 0
Check 2: One Record per Subject
DM %>%
count(USUBJID) %>%
filter(n > 1)# A tibble: 0 × 2
# ℹ 2 variables: USUBJID <chr>, n <int>
Check 3: Missing Required Variables
DM %>%
summarise(
missing_STUDYID = sum(is.na(STUDYID)),
missing_USUBJID = sum(is.na(USUBJID)),
missing_SEX = sum(is.na(SEX))
)# A tibble: 1 × 3
missing_STUDYID missing_USUBJID missing_SEX
<int> <int> <int>
1 0 0 0
Check 4: Required variable presence
required_vars <- c(
"STUDYID",
"DOMAIN",
"USUBJID",
"SUBJID"
)
required_vars %in% names(DM)[1] TRUE TRUE TRUE TRUE
Export SDTM Dataset
write_csv(
DM,
"SDTM_DM.csv"
)Key Learning
Clinical programming involves much more than renaming variables. It requires transforming raw clinical data into standardized SDTM datasets by applying CDISC standards, controlled terminology, derivations, and validation checks.
During Raw → SDTM transformation, we:
- Map source variables to CDISC SDTM structures
- Standardize variable names
- Convert dates into ISO 8601 format
- Apply controlled terminology
- Derive unique subject identifiers (USUBJID)
- Perform quality checks
- Prepare analysis-ready regulatory datasets
Conclusion
In this article, we transformed a raw demographics dataset into an SDTM DM domain using R. We reviewed the source data, mapped variables to SDTM standards, standardized dates and controlled terminology, validated the resulting dataset, and exported the final SDTM DM dataset. This workflow provides a practical foundation for understanding SDTM development in R before moving on to metadata-driven programming and additional SDTM domains.
In the next article, we’ll explore metadata-assisted SDTM programming using a metadata specification and sdtm.oak. We’ll see how metadata can improve consistency, traceability, and maintainability in SDTM development before moving towards formal metadata frameworks such as metacore.